<?php
/**
 * Table object
 */
class SRDO_Table {
	public $table;
	public $database	= null;
	public $alias		= null;

	/**
	 * Set a table...
	 */
	public function __construct($table, $alias = null, $database = null) {
		$this->table = $table;

		if ( !isset(SRDO::$config['table'][$this->table]) ) {
			SRDO::$config['table'][$this->table] = array(	'table'	=> $this->table
															);
		}

		if ($alias) {
			$this->alias = $alias;
		}

		if ( $database ) {
			$this->database = $database;
			SRDO::$config['table'][$this->table]['database'] = $this->database;
		}
	}

	/** 
	 * Specifies a field and it's type
	 * @return  void
	 */
	public function F($field, $type, $insert = null, $update = null) { //, $var = null)
		if ( is_a($type, 'SRDO_SQL') ) {
			// We're going to generate a unique type
			$t = 'SQL-' . $this->table . '-' . $field;
			$tmp = new SRDO_Type($t);
			$tmp->sql($type->sql);
			$type = $t;
		}

		SRDO::$config['table'][$this->table]['field'][$field] = array(	'type'	=> $type,
								// Are we 'importing' foreign primary keys?
								($type == '!PK' ? 'table' : 'field')	=> $field,
								'insert' 	=> $create,
								'update'	=> $update );
	}

	/**
	 * Defines a primary key
	 */
	public function PK($field, $type) {
		$this->F($field, $type);
		$this->P($field);
	}
	
	/**
	 * Defines a foreign key
	 */
	public function FK($table, $keys = null, $var = null, $primary = false) {
		$load = false;
		if ( $var === null ) {
			$var = $table;
		} elseif ( substr($var, 0, 1) == '+' ) {
			$load 	= true;
			$var 	= substr($var, 1);
		}

		$map = array();

		if ( $keys !== null ) {
			// We have the field names (local=foreign)
			if ( !is_array($keys) ) {
				$keys = array($keys);
			}

			foreach ( $keys AS $key ) {
				if ( $key == '*' ) {
					$map['*'] = $table;
				} else {
					list($field, $fk) 	= explode('=', $key);
					$map[$field] 		= $fk;
				}
			}
		} else {
			$map['*'] = $table;
		}

		$field = array();
		
		// See if our table exists (otherwise we'll delay)
		if ( is_array(SRDO::$config['table'][$table]) ) {
			if ( !$primary ) {
				SRDO::$config['table'][$table]['referenced_by'][$this->table] = $this->table;
			}
			// It exists, we're going to get it's keys
			foreach ( $map AS $f => $fk ) {
				if ( $f == '*' ) {
					if ( is_array(SRDO::$config['table'][$table]['primary']) ) {
						foreach ( SRDO::$config['table'][$table]['primary'] AS $pk ) {
							if ( !isset($field[$pk]) && !in_array($pk, $field) ) {
								$field[$pk] 		= SRDO::$config['table'][$table]['field'][$pk];
								$field[$pk]['fk'] 	= $pk;
								$field[$pk]['table']= $table;
							}
						}
					}
				} else {
					$field[$f] 			= SRDO::$config['table'][$table]['field'][$fk];
					$field[$f]['field'] = $f;
					$field[$f]['fk'] 	= $fk;
					$field[$f]['table']	= $table;
				}
			}
		} else {
			$field = array($var => array(	'type' 	=> '!FK', 
											'table' => $table,
											'load'	=> $load
											));
		}

		// Now add all of the fields we found to this table
		foreach ( $field AS $f => $v ) {
			if ( !isset(SRDO::$config['table'][$this->table]['field'][$f]) ) {
				// Add our key here to this table
				SRDO::$config['table'][$this->table]['field'][$f] = $v;
			}

			// It's also a pk
			if ( $primary ) {
				SRDO::$config['table'][$this->table]['primary'][$f] = $f;
			}

			// We have a foreign key (object) and we are setting it as a member variable
			if ( isset($v['fk']) && $var == '!join' ) {
				SRDO::$config['table'][$this->table]['field'][$f]['join'] = true;
			} elseif ( isset($v['fk']) && $var == '!false' ) {
				SRDO::$config['table'][$this->table]['field'][$f]['join'] = false;
			} elseif ( isset($v['fk']) ) {
				if ( !isset(SRDO::$config['table'][$this->table]['var'][$var]) ) {
					SRDO::$config['table'][$this->table]['var'][$var] = array(	'var'	=> $var,
																		'table'	=> $table,
																		'load'	=> $load,
																		'map'	=> array()
																		);
				}
				//list(,$fk) = explode('.', $v['fk']);
				SRDO::$config['table'][$this->table]['var'][$var]['map'][$f] = $v['fk'];
			}
		}

		// Return the key names of the fields we added
		return array_keys($field);
	}

	/**
	 * This is a var name for our SRDO_Object
	 */
	public function V($table, $keys, $var = null) {
		$load = false;
		if ($var === null) {
			$var = $table;
		} elseif (substr($var, 0, 1) == '+') {
			$load 	= true;
			$var 	= substr($var, 1);
		}

		$map = array();

		if (!is_array($keys)) {
			$keys = array($keys);
		}

		foreach ($keys AS $key) {
			list($field, $fk) 	= explode('=', $key);
			$map[$field] 		= $fk;
		}

		SRDO::$config['table'][$this->table]['var'][$var] = array(	'var'	=> $var,
															'table'	=> $table,
															'load'	=> $load,
															'map'	=> $map
															);
	}


	/**
	 * Resolves all !PK and !FK types and imports the config data
	 */
	private function setupField($table, $field) {
		$tbl 	= &SRDO::$config['table'][$table];
		$ft		= $tbl['field'][$field]['table'];
		$primary= ($tbl['field'][$field]['type'] == '!PK');

		if (!$ft || !isset(SRDO::$config['table'][$ft])) {
			return false;
		}

		if (!$primary) {
			// Register us with the foreign table's 'referenced_by' array
			SRDO::$config['table'][$ft]['referenced_by'][$table] = $table;
		}

		$f = array();
		// Now get the foreign pk's
		foreach (SRDO::$config['table'][$ft]['primary'] AS $pk) {
			$f[$pk] = SRDO::$config['table'][$ft]['field'][$pk];
			$f[$pk]['fk'] 	= $pk;
			$f[$pk]['table']= $ft;

			if ($primary) {
				SRDO::$config['table'][$table]['primary'][$pk] = $pk;
				$f[$pk]['join'] = true;
			}
		}

		// Now that we have they keys, we do a little song and dance
		$tmp = array();
		foreach ($tbl['field'] AS $k => $v) {
			// Copy an existing field over
			if ($k != $field) {
				$tmp[$k] = $v;
				continue;
			}

			if (isset($tbl['unique'][$v['table']])) {
				$tbl['unique'][$v['table']] = array_keys($f);
			} elseif (isset($tbl['index'][$v['table']])) {
				$tbl['index'][$v['table']] = array_keys($f);
			}

			// Merge in our primary keys here
			$map = array();
			foreach ($f AS $kk => $vv) {
				$tmp[$kk] 	= $vv;
				$map[$kk] 	= $kk;
			}

			// Now register this object
			$tbl['var'][$k] = array(	'var'	=> $k,
										'table'	=> $ft,
										'load'	=> $v['load'],
										'map'	=> $map
										);
		}
		$tbl['field'] = $tmp;

		return true;
	}


	// Join table
	/**
	 * Create a many-to-many join, we're not going to actually create an object for the join table, but add the join
	 * statement to the foreign table object so all functionality is inherited. When dealing with a foreign object, 
	 * only the relationships are created, updated, deleted. Call getForeignObject() to get the actual object
	 *
	 * @return	Object	SRDO_Table
	 */
	public function &J($table, $var = null) {
		if (!is_array($table)) {
			$table = array($table);
		}

		$rel 	= '';
		$tmp	= array();
		foreach ($table AS $t) {
			$alias = $t;
			if (strpos($t, ':')) {
				list($t, $alias) = explode(':', $t);
			}
			//$alias	 = (strpos($t, ':') ? substr($t, strpos($t, ':') + 1) : $t);
			$rel 	.= ($rel ? '_' : '') . $alias;
			/*
			if ($n) {
				SRDO::$config['table'][$joinTable]['var'][$alias] = array(	'var'	=> $alias,
															'table'	=> $t,
															'load'	=> false,
															'map'	=> SRDO::$config['table'][$t]['primary']
															);
			}
			*/
			$tmp[$t] = $alias;
		}
		$table = $tmp;

		$joinTable 	= $this->table . '2' . $rel;
		$load 		= false;
		if ($var === null) {
			$var 	= $rel;
		} elseif (substr($var, 0, 1) == '+') {
			$var 	= substr($var, 1);
			$load	= true;
		}

		if ($this->database) {
			$db = new SRDO_Database($this->database);
			$jt = &$db->T($joinTable);
		} else {
			$jt = new SRDO_Table($joinTable);
		}

		SRDO::$config['table'][$joinTable]['parent'] = $this->table;

		// Add our keys here (as PKs, why we pass in 'true')
		$jt->FK($this->table, '*', '!false', true);

		// Register this joinTable (var) with the parent table
		SRDO::$config['table'][$this->table]['var'][$var] = array(	'var'	=> $var,
															'table'	=> $joinTable,
															'load'	=> $load,
															'map'	=> SRDO::$config['table'][$this->table]['primary']
															);
		$n = (count($table) > 1);
		if (!$n) {
			$tmp = array_keys($table);
			SRDO::$config['table'][$this->table]['var'][$var]['object'] = $tmp[0];
		}

		foreach ($table AS $tbl => $alias) {
			if ($n) {
				//$jt->F($tbl, '!PK');
				$jt->FK($tbl, '*', '!join', true);
				SRDO::$config['table'][$joinTable]['var'][$alias] = array(	'var'	=> $alias,
																	'table'	=> $tbl,
																	'load'	=> false,
																	'map'	=> SRDO::$config['table'][$t]['primary']
																	);
			} else {
				if (isset(SRDO::$config['table'][$tbl])) {
					$jt->FK($tbl, '*', '!join', true);
				} else {
					$jt->F($tbl, '!PK');
				}
			}

		}

		return $jt;
	}
	
	/**
	 * Specifies an index
	 */
	public function I($fields, $index = null) {
		if (!is_array($fields)) {
			$fields = explode(' ', $fields);
		}

		$index = ($index === null ? implode('_', $fields) : $index);
		foreach ($fields AS $f) {
			SRDO::$config['table'][$this->table]['index'][$index][$f] = $f;
		}
	}

	/**
	 * Specifies a uniueq index
	 */
	public function U($fields, $index = null) {
		if (!is_array($fields)) {
			$fields = explode(' ', $fields);
		}

		$index = ($index === null ? implode('_', $fields) : $index);
		foreach ($fields AS $f) {
			SRDO::$config['table'][$this->table]['unique'][$index][$f] = $f;
		}
	}

	/**
	 * Adds a field to the primary key
	 */
	public function P($fields) {
		if (!is_array($fields)) {
			$fields = explode(' ', $fields);
		}

		foreach ($fields AS $f) {
			SRDO::$config['table'][$this->table]['primary'][$f] = $f;
		}
	}

	/**
	 * Returns a table's array structure
	 */
	public function getTable($table) {
		if (isset(SRDO::$config['table'][$table])) {
			if (!isset(SRDO::$config['table'][$table]['validate'])) {
				SRDO_Table::validate($table);
			}

			return SRDO::$config['table'][$table];
		}

		return false;
	}

	/**
	 * Returns a tables field array structure
	 */
	public function getFields($table) {
		if (isset(SRDO::$config['table'][$table])) {
			if (!isset(SRDO::$config['table'][$table]['validate'])) {
				SRDO_Table::validate($table);
			}

			return SRDO::$config['table'][$table]['field'];
		}

		SRDO::error("getFields($table): Returning false");
		return false;
	}

	/**
	 * Returns a table's primary keys
	 */
	public function getKeys($table) {
		if (isset(SRDO::$config['table'][$table])) {
			if (!isset(SRDO::$config['table'][$table]['validate'])) {
				SRDO_Table::validate($table);
			}

			return SRDO::$config['table'][$table]['primary'];
		}

		return false;
	}

	public function getVars($table) {
		if (isset(SRDO::$config['table'][$table])) {
			if (!isset(SRDO::$config['table'][$table]['validate'])) {
				SRDO_Table::validate($table);
			}

			return SRDO::$config['table'][$table]['var'];
		}

		return false;
	}

	public function getCreateTable($table = null, $drop = false) {
		if (!$table && !$this) {
			return false;
		} elseif (!$table) {
			$table = $this->table;
		}

		$t = &SRDO::$config['table'][$table];
		if (!isset(SRDO::$config['table'][$table]['validate'])) {
			SRDO_Table::validate($table);
		}

		if ($drop) {
			$sql = 'DROP TABLE IF EXISTS `' . $table . "`;\n";
		}
		$sql .= 'CREATE TABLE IF NOT EXISTS `' . $table . "` (\n";
		foreach ($t['field'] AS $k => $v) {
			$sql .= sprintf("\t%s %s,\n", 
							$v['field'], 
							(isset($v['fk']) ?
								SRDO_Type::getFkSQL($v['type'])
								:
								SRDO_Type::getSQL($v['type']))
							);
		}

		// Primary Keys
		$tmp = array();
		if ($t['primary']) {
			$tmp[] = "\tPRIMARY KEY(`" . implode('`, `', $t['primary']) . "`)";
		}

		if ($t['index']) {
			foreach ($t['index'] AS $n => $keys) {
				$tmp[] = "\tKEY `$n` (`" . implode('`, `', $keys) . "`)";
			}
		}

		if ($t['unique']) {
			foreach ($t['unique'] AS $n => $keys) {
				$tmp[] = "\tUNIQUE KEY `$n` (`" . implode('`, `', $keys) . "`)";
			}
		}

		$sql .= implode(",\n", $tmp) . "\n";

		$sql .= ")";

		if ($t['set']['mysql.type']) {
			$sql .= " Type={$t['set']['mysql.type']}";
		}
		$sql .= ";\n";

		return $sql;
	}

	public function compareSchema($table) {
		$oT = &SRDO_Object::factory($table);
		$oDB = &$oT->getDatabaseConnection();

		if ($oDB) {
			$st = $oDB->prepare('SHOW CREATE TABLE ' . $table);
			
			if ( $st->execute() ) {
				$tmp = $st->fetch( PDO::FETCH_ASSOC );

				$field	= array();
				$index	= array();
				$lines 	= explode("\n", $tmp['Create Table']);
				foreach ($lines AS $line) {
					$line = trim($line);
					if (substr($line, -1) == ',') {
						$line = substr(trim($line), 0, -1);
					}

					if (preg_match('/^CREATE TABLE/', $line)) {
						continue;
					} elseif (preg_match('/^`([a-z0-9_]+)` (.+)\s?(?:default (.*))?$/Ui', $line, $m)) {
						$field[$m[1]] = strtoupper($m[2]) . (isset($m[3]) && $m[3] != "''" ? " DEFAULT {$m[3]}" : '');
					} elseif (preg_match('/(PRIMARY|UNIQUE)?\s*KEY\s*(`\w+`)?\s+\(((?:`\w+`,?\s*)+)\)$/', $line, $m)) {
						//print_r($m);
					}
					// Handle indexes
				}
				
				$tbl 	= $oT->table();

				$local = array();
				foreach ($tbl['field'] AS $v) {
					$local[] = $v['field'];
				}
				$db		= array_keys($field);

				$action = array();
				foreach (array_diff($local, $db) AS $k) {
					$action[$k] = array(	'action'	=> '+',
											'sql'		=> ''
											);
				}
				foreach (array_diff($db, $local) AS $k) {
					$action[$k] = array(	'action'	=> '-',
											'sql'		=> 'DROP COLUMN `' . $k . '`'
											);
				}

				$prev = '';
				foreach ($tbl['field'] AS $k => $v) {
					$sql = (isset($v['fk']) ?
								SRDO_Type::getFkSQL($v['type'])
								:
								SRDO_Type::getSQL($v['type'])
								);

					if (isset($action[$k])) {
						if ($action[$k]['action'] == '+') {
							$tmp = 'ADD COLUMN `' . $k . '` ' . $sql . ' ' . ($prev != '' ? 'AFTER `' . $prev . '`' : 'FIRST');
							$action[$k]['sql'] = $tmp;
						}
					} elseif (trim($sql) != trim($field[$k])) {
						// Check if it's just an index thing
						if (preg_match('/(.*)\s+UNIQUE\s*$/i', $sql, $match) && ($match[1] != trim($field[$k]))) {
						$action[$k] = array(	'action'	=> '~',
												'sql'		=> 'CHANGE COLUMN `' . $k . '` `' . $k . '` ' . $sql,
												'sql_local'	=> $sql,
												'db_sql'	=> $field[$k]
												);
						}
					}

					$prev = $k;
				}

				return $action;
			}
		}

		
		return false;
	}

	/**
	 * Verifies a table's structure
	 */
	public function validate($table = null) {
		if (!$table && !$this) {
			return false;
		} elseif (!$table) {
			$table = $this->table;
		}

		if (!isset(SRDO::$config['table'][$table])) {
			return false;
		}

		if (SRDO::$config['table'][$table]['validate']) {
			return SRDO::$config['table'][$table]['validate'];
		}

		$ret = true;
		// Go through and verify our integrity constraints
		foreach (SRDO::$config['table'][$table]['field'] AS $k => $v) {
			if ($v['type'] == '!FK' || $v['type'] == '!PK') {
				if ( !self::setupField($table, $k) ) {
					$ret = false;
					SRDO::error(sprintf('Failed to setup field(s) \'%s.%s\' on \'%s\'',
											$table, $k, $k));
				}
			}
		}

		foreach (SRDO::$config['table'][$table]['field'] AS $k => $v) {
			if (!isset(SRDO::$config['type'][$v['type']])) {
				SRDO::error(sprintf('Unknown type \'%s\' for field \'%s.%s\'',
											$v['type'], $table, $k));
				$ret = false;
			}
		}

		SRDO::$config['table'][$table]['validate'] = $ret;

		return $ret;
	}

	public function set($key, $value) {
		SRDO::$config['table'][$this->table]['set'][$key] = $value;
	}
}